import * as THREE from "three";
const tankTextrueMap = {
  TOP: new THREE.TextureLoader().load("./tank2U.png"),
  RIGHT: new THREE.TextureLoader().load("./tank2R.png"),
  DOWN: new THREE.TextureLoader().load("./tank2D.png"),
  LEFT: new THREE.TextureLoader().load("./tank2L.png"),
};
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
const tankBatle = document.querySelector("#game");
const renderer = new THREE.WebGLRenderer({
  preserveDrawingBuffer: true,
});
renderer.setSize(window.innerWidth, window.innerHeight);
const gameGroup = new THREE.Group();
scene.add(gameGroup);
camera.position.x = 700;
camera.position.y = -430;
camera.position.z = 600;
camera.lookAt(700, -430, 0);
tankBatle.appendChild(renderer.domElement);

init();
animate();
function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

async function init() {
  let wsClient = createWs();
  let moveType = "STOP";
  document.onkeydown = function ({ code }) {
    let shoot = false;
    if (code === "ArrowUp") {
      moveType = "TOP";
    } else if (code === "ArrowDown") {
      moveType = "DOWN";
    } else if (code === "ArrowLeft") {
      moveType = "LEFT";
    } else if (code === "ArrowRight") {
      moveType = "RIGHT";
    } else if (code === "KeyS") {
      moveType = "STOP";
    } else if (code === "Space") {
      shoot = true;
    } else {
      return;
    }
    wsClient.send(
      JSON.stringify({
        url: "move",
        data: { moveType, shoot },
      })
    );
  };
  const tanksGroup = await createTankGroup();
  const bulletGrop = createBulletGroup();
  const safeArea = createSafeArea();
  const props = createProp();
  gameGroup.add(tanksGroup.group);
  gameGroup.add(bulletGrop.group);
  gameGroup.add(safeArea.group);
  gameGroup.add(props.group);
  function createWs() {
    const ws = new WebSocket("ws://localhost:9999");
    ws.onmessage = function (evt) {
      const { cmd, data } = JSON.parse(evt.data) || {};
      console.info(cmd);
      if (cmd === "start") {
        gameGroup.add(createBattleField(data));
      } else if (cmd === "action") {
        render_(data);
      } else if (cmd === "end") {
        console.info("gg");
      }
    };
    ws.onclose = () => {
      console.info("close");
      // wsClient = createWs();
    };
    ws.onopen = () => {
      ws.send(JSON.stringify({ url: "init", data: false }));
    };
    window.onbeforeunload = () => {
      ws.close();
    };
    return ws;
  }
  function render_(data) {
    //更新坦克信息或者创建坦克
    tanksGroup.update(data.tankSatesVoList);
    bulletGrop.update(data.bulletSateVoList);
    safeArea.update(data.safeAreaStateVo);
    props.update(data.propVoList);
  }
}
function createBattleField(map) {
  const mapGroup = new THREE.Group();
  for (let i = 0; i < Math.floor(860 / 50); i++) {
    for (let j = 0; j < 1400 / 50; j++) {
      const pos = coordinateTransformation(j * 50 + 50 / 2, i * 50 + 50 / 2);
      const cube = createMapBlock("empty", pos.x, pos.y);
      mapGroup.add(cube);
    }
  }
  const typeMap = {
    B: "brick",
    I: "iron",
    T: "tree",
    W: "water",
  };
  for (let i = 0; i < map.length; i += 10) {
    const cur = [];
    if (i + 10 >= map.length) {
      break;
    }
    for (let j = 0; j < map[i].length; j += 10) {
      const x = (j / 10) * 50 + 25;
      const y = (i / 10) * 50 + 25;
      const pos = coordinateTransformation(x, y);
      if (map[i][j] in typeMap) {
        mapGroup.add(createMapBlock(typeMap[map[i][j]], pos.x, pos.y));
      } else {
      }
    }
  }
  return mapGroup;
}

function createTankGroup() {
  const group = new THREE.Group();
  const nameCache = new Set();

  function createLabel(name, hp, a, d) {
    const ele = document.querySelector("#text");
    const firstRow = `h:${hp} a:${a} d:${d}`;
    ele.innerHTML = `${firstRow}`;
    const firstRowLength = firstRow.split("").reduce((pre, cur) => {
      return pre + (String(cur).charCodeAt() > 255 ? 2 : 1);
    }, 0);

    return html2canvas(ele, { dpi: 1920 }).then((canvas) => {
      const map = new THREE.TextureLoader().load(canvas.toDataURL());
      const material = new THREE.MeshBasicMaterial({
        map: map,
        transparent: true,
      });
      const geometry = new THREE.PlaneGeometry(
        Math.max(firstRowLength) * 7.5,
        20
      );
      const sprite = new THREE.Mesh(geometry, material);
      sprite.name = "label";
      sprite.position.y = -10;
      return sprite;
    });
  }
  async function createTank(
    tank = {
      atk: 10,
      def: 5,
      height: 50,
      hp: 100,
      movingDirect: "TOP",
      tankId: "host",
      width: 50,
      x: 475,
      y: 275,
    }
  ) {
    const geometry = new THREE.PlaneGeometry(50, 50);
    const material = new THREE.MeshBasicMaterial({
      map: tankTextrueMap[tank.movingDirect],
      side: THREE.DoubleSide,
      color: tank.tankId == "host" ? "red" : "transparent",
      transparent: true,
    });
    const pos = coordinateTransformation(tank.x, tank.y);
    const cube = new THREE.Mesh(geometry, material);
    const label = await createLabel(tank.tankId, tank.hp, tank.atk, tank.def);
    cube.userData.info = [tank.hp, tank.atk, tank.def].join("-");
    cube.add(label);
    cube.name = tank.tankId;
    cube.position.x = pos.x;
    cube.position.y = pos.y;
    cube.position.z = 0;
    return cube;
  }
  return {
    async update(tanklist) {
      for (let tank of tanklist) {
        if (nameCache.has(tank.tankId)) {
          const tankObj = group.getObjectByName(tank.tankId);
          if (!tankObj) continue;
          if (tank.hp === 0) {
            group.remove(tankObj);
            nameCache.delete(tank.tankId);
            continue;
          }
          tankObj.material.map.dispose();
          tankObj.material.map = tankTextrueMap[tank.movingDirect];
          const pos = coordinateTransformation(tank.x, tank.y);
          tankObj.position.x = pos.x;
          tankObj.position.y = pos.y;
          const info = [tank.hp, tank.atk, tank.def].join("-");
          if (tankObj.userData.info !== info) {
            tankObj.clear();
            const label = await createLabel(
              tank.tankId,
              tank.hp,
              tank.atk,
              tank.def
            );
            tankObj.add(label);
            tankObj.userData.info = info;
          }
        } else if (tank.hp > 0) {
          nameCache.add(tank.tankId);
          const tank_ = await createTank(tank);
          group.add(tank_);
        }
      }
      //清理被销毁的坦克
      for (let name of nameCache) {
        if (!tanklist.find((tank) => tank.tankId === name)) {
          const tankObj = group.getObjectByName(name);
          tankObj && group.remove(tankObj);
          nameCache.delete(name);
        }
      }
    },
    group,
  };
}
function createBulletGroup() {
  const group = new THREE.Group();
  const nameCache = new Set();
  function create(bullet) {
    const width =
      bullet.movingDirect === "LEFT" || bullet.movingDirect === "RIGHT"
        ? 10
        : 5;
    const height =
      bullet.movingDirect === "LEFT" || bullet.movingDirect === "RIGHT"
        ? 5
        : 10;

    const geometry = new THREE.PlaneGeometry(width, height);
    const material = new THREE.MeshBasicMaterial({
      color: "red",
    });
    const cube = new THREE.Mesh(geometry, material);
    cube.name = bullet.bulletId;
    const pos = coordinateTransformation(bullet.x, bullet.y);
    cube.position.x = pos.x;
    cube.position.y = pos.y;
    cube.position.z = 0;
    return cube;
  }
  return {
    update(bulletList) {
      for (let bullet of bulletList) {
        if (nameCache.has(bullet.bulletId)) {
          const bulletObj = group.getObjectByName(bullet.bulletId);
          const pos = coordinateTransformation(bullet.x, bullet.y);
          bulletObj.position.x = pos.x;
          bulletObj.position.y = pos.y;
        } else {
          nameCache.add(bullet.bulletId);
          group.add(create(bullet));
        }
      }
      for (let name of nameCache) {
        if (!bulletList.find((bullet) => bullet.bulletId === name)) {
          const bulletObj = group.getObjectByName(name);
          bulletObj && group.remove(bulletObj);
          nameCache.delete(name);
        }
      }
    },
    group,
  };
}
function createSafeArea() {
  const group = new THREE.Group();
  let r = -1;
  function create(x, y, r) {
    const geometry = new THREE.RingGeometry(r, r + 1, 100);
    const material = new THREE.MeshBasicMaterial({
      color: "red",
      side: THREE.DoubleSide,
    });
    const cube = new THREE.Mesh(geometry, material);
    cube.name = "safeArea";
    cube.position.x = x;
    cube.position.y = y;
    cube.position.z = 2;
    return cube;
  }
  return {
    update(safeAreaStateVo) {
      const pos = coordinateTransformation(
        safeAreaStateVo.x,
        safeAreaStateVo.y
      );
      if (r === -1) {
        group.add(create(pos.x, pos.y, safeAreaStateVo.r));
        r = safeAreaStateVo.r;
      } else if (safeAreaStateVo.r !== r) {
        const obj = group.getObjectByName("safeArea");
        if (obj) {
          obj.scale.set(safeAreaStateVo.r / r, safeAreaStateVo.r / r, 1);
          obj.position.x = pos.x;
          obj.position.y = pos.y;
        }
      }
    },
    group,
  };
}
function createProp() {
  const group = new THREE.Group();
  const nameCache = new Set();
  const textrueMap = {
    ATTACK: "./attack.png",
    BLOOD: "./blood.png",
    DEFENSE: "./defense.png",
  };
  function create(x, y, type, id) {
    const geometry = new THREE.PlaneGeometry(50, 50);
    const material = new THREE.MeshBasicMaterial({
      map: new THREE.TextureLoader().load(textrueMap[type]),
      side: THREE.DoubleSide,
      transparent: true,
    });
    const cube = new THREE.Mesh(geometry, material);
    cube.name = id;
    cube.position.x = x;
    cube.position.y = y;
    cube.position.z = 0;
    return cube;
  }
  return {
    update(propVoList) {
      const idxMap = {
        ATTACK: 0,
        BLOOD: 0,
        DEFENSE: 0,
      };
      const props = propVoList.map((propVo) => {
        const pos = coordinateTransformation(propVo.x, propVo.y);
        return {
          ...propVo,
          x: pos.x,
          y: pos.y,
          id: propVo.propTypeEnums + "_" + idxMap[propVo.propTypeEnums]++,
        };
      });

      for (let propVo of props) {
        if (nameCache.has(propVo.id)) {
          const propObj = group.getObjectByName(propVo.id);
          propObj.position.x = propVo.x;
          propObj.position.y = propVo.y;
        } else {
          nameCache.add(propVo.id);
          group.add(
            create(propVo.x, propVo.y, propVo.propTypeEnums, propVo.id)
          );
        }
      }
      for (let name of nameCache) {
        if (!props.find((propVo) => propVo.id === name)) {
          const propObj = group.getObjectByName(name);
          propObj && group.remove(propObj);
          nameCache.delete(name);
        }
      }
    },
    group,
  };
}
function createMapBlock(type, x, y) {
  const textrueMap = {
    iron: "./fe.png",
    empty: "./empty.png",
    tree: "./tree.png",
    water: "./water.png",
    brick: "./zt.png",
  };
  const geometry = new THREE.PlaneGeometry(50, 50);
  const material = new THREE.MeshBasicMaterial({
    map: new THREE.TextureLoader().load(textrueMap[type]),
    side: THREE.DoubleSide,
  });
  const cube = new THREE.Mesh(geometry, material);
  cube.position.x = x;
  cube.position.y = y;
  cube.position.z = 0;
  return cube;
}

function coordinateTransformation(x, y) {
  return new THREE.Vector2(x, -y);
}
